package org.gdstash.file;

import org.gdstash.util.GDConstants;
import org.gdstash.util.GDMsgFormatter;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.Charset;

public class GDWriter {
    private static int key;
    private static int[] table;
    private static byte[] buffer;
    private static int pos;

    public static void reserveBuffer(final GDFileSize gfs) {
        GDWriter.buffer = new byte[gfs.getByteSize()];
        GDWriter.pos = 0;
    }

    public static void writeBuffer(final File file) throws IOException {
        try (final FileOutputStream writer = new FileOutputStream(file)) {
            writer.write(GDWriter.buffer, 0, GDWriter.pos);
            writer.flush();
        } catch (IOException ex) {
            throw ex;
        }
    }

    public static void writeByteArr(final byte[] b) throws IOException {
        if (GDWriter.pos + b.length > GDWriter.buffer.length) {
            throw new EOFException(GDMsgFormatter.getString(GDMsgFormatter.rbMsg, "ERR_UNEXPECTED_EOF"));
        }
        for (int i = 0; i < b.length; ++i) {
            GDWriter.buffer[GDWriter.pos + i] = b[i];
        }
        GDWriter.pos += b.length;
    }

    public static void writeBlockStart(final GDReader.Block block, final int n) throws IOException {
        writeInt(n);
        writeInt(0);
        block.end = GDWriter.pos;
    }

    public static void writeBlockEnd(final GDReader.Block block) throws IOException {
        final int currPos = GDWriter.pos;
        GDWriter.pos = block.end - 4;
        writeInt(currPos - block.end);
        GDWriter.pos = currPos;
        writeInt(0);
    }

    public static void writeByte(final byte b) throws IOException {
        final byte[] bArr = {b};
        writeByteArr(bArr);
    }

    public static void writeFloat(final float value) throws IOException {
        final int i = Float.floatToIntBits(value);
        writeInt(i);
    }

    public static void writeInt(final int value) throws IOException {
        final byte[] b = intToBytes4(value);
        writeByteArr(b);
    }

    public static void writeStringBytes(final byte[] bArr) throws IOException {
        if (bArr == null) {
            writeInt(0);
            return;
        }
        final int len = bArr.length;
        writeInt(len);
        writeByteArr(bArr);
    }

    public static void writeString(final String s) throws IOException {
        if (s == null) {
            writeInt(0);
            return;
        }
        final byte[] b = s.getBytes(GDConstants.CHARSET_STASH);
        final int len = b.length;
        writeInt(len);
        writeByteArr(b);
    }

    public static void writeStringUByte(final FileOutputStream writer, final String s) throws IOException {
        writeStringUByte(writer, s, GDConstants.CHARSET_STASH);
    }

    public static void writeStringUByte(final FileOutputStream writer, final String s, final Charset cs) throws IOException {
        if (s == null) {
            writer.write(0);
            return;
        }
        final byte[] b = s.getBytes(cs);
        writer.write(b.length);
        writer.write(b);
    }

    public static void writeWideString(final String s) throws IOException {
        if (s == null) {
            writeInt(0);
            return;
        }
        final int len = s.length();
        writeInt(len);
        final byte[] b = s.getBytes(GDConstants.CHARSET_WIDE);
        writeByteArr(b);
    }

    public static void writeKey(final FileOutputStream writer, final int key) throws IOException {
        GDWriter.key = key;
        GDWriter.table = GDReader.buildTable(GDWriter.key);
        final int k = key ^ 0x55555555;
        final byte[] b = intToBytes4(k);
        writer.write(b);
    }

    public static void writeCurrentKey(final FileOutputStream writer) throws IOException {
        writeEncInt(writer, GDWriter.key, false);
    }

    private static void updateKey(final int value) {
        final byte[] b = ByteBuffer.allocate(4).putInt(value).array();
        for (int i = 0; i < b.length; ++i) {
            int j = b[i];
            if (j < 0) {
                j += 256;
            }
            GDWriter.key ^= GDWriter.table[j];
        }
    }

    private static void updateKey(final byte value) {
        final byte[] b = {value};
        for (int i = 0; i < b.length; ++i) {
            int j = b[i];
            if (j < 0) {
                j += 256;
            }
            GDWriter.key ^= GDWriter.table[j];
        }
    }

    public static void writeEncByte(final FileOutputStream writer, final byte value) throws IOException {
        final byte[] b = {(byte) (value ^ GDWriter.key)};
        updateKey(value);
        writer.write(b);
    }

    public static void writeEncFloat(final FileOutputStream writer, final float value) throws IOException {
        final int i = Float.floatToIntBits(value);
        writeEncInt(writer, i, true);
    }

    public static void writeEncInt(final FileOutputStream writer, final int value, final boolean updateKey) throws IOException {
        final int enc = value ^ GDWriter.key;
        if (updateKey) {
            updateKey(value);
        }
        final byte[] b = intToBytes4(enc);
        writer.write(b);
    }

    public static void writeEncString(final FileOutputStream writer, final String s) throws IOException {
        if (s == null) {
            writeEncInt(writer, 0, true);
            return;
        }
        writeEncInt(writer, s.length(), true);
        final char[] c = s.toCharArray();
        for (int i = 0; i < c.length; ++i) {
            final byte b = (byte) c[i];
            writeEncByte(writer, b);
        }
    }

    public static void writeByte(final OutputStream os, final byte value) throws IOException {
        os.write(value);
    }

    public static void writeBytes(final OutputStream os, final byte[] value) throws IOException {
        os.write(value);
    }

    public static void writeInt(final OutputStream os, final int value) throws IOException {
        final byte[] b = intToBytes4(value);
        os.write(b);
    }

    public static void writeString(final OutputStream os, final String s) throws IOException {
        if (s == null) {
            writeInt(os, 0);
            return;
        }
        writeInt(os, s.length());
        final char[] c = s.toCharArray();
        for (int i = 0; i < c.length; ++i) {
            final byte b = (byte) c[i];
            writeByte(os, b);
        }
    }

    public static String getOSFilePath(String filename) {
        String osName = "";
        int pos = 0;
        while (pos != -1) {
            pos = filename.indexOf("/");
            if (pos != -1) {
                osName = osName + filename.substring(0, pos) + System.getProperty("file.separator");
                filename = filename.substring(pos + 1);
            } else {
                osName += filename;
            }
        }
        return osName;
    }

    public static String getOSDirPath(final String filename) {
        String osPath = null;
        final int pos = filename.lastIndexOf(System.getProperty("file.separator"));
        if (pos != -1) {
            osPath = filename.substring(0, pos + 1);
        }
        return osPath;
    }

    public static int lengthToInt(final String s) {
        int i = 0;
        if (s == null) {
            return i;
        }
        i = s.getBytes().length;
        return i;
    }

    public static byte[] lengthToBytes4(final String s) {
        if (s == null) {
            return new byte[4];
        }
        return intToBytes4(s.length());
    }

    public static byte[] intToBytes4(final int value) {
        final byte[] b = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(value).array();
        return b;
    }

    public static byte[] floatToBytes4(final float value) {
        final int i = Float.floatToIntBits(value);
        return intToBytes4(i);
    }

    public static void write(final String filename, final byte[] data) throws IOException {
        final String dirName = getOSDirPath(filename);
        final File dir = new File(dirName);
        if (!dir.exists() && !dir.mkdirs()) {
            final Object[] args = {dirName};
            final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_DIR_CREATE_FAIL", args);
            throw new IOException(msg);
        }
        final File file = new File(filename);
        file.createNewFile();
        try (final FileOutputStream writer = new FileOutputStream(file)) {
            writer.write(data);
            writer.flush();
        } catch (IOException ex) {
            throw ex;
        }
    }

    public static void write(final String filename, final String data) throws IOException {
        final String dirName = getOSDirPath(filename);
        final File dir = new File(dirName);
        if (!dir.exists() && !dir.mkdirs()) {
            final Object[] args = {dirName};
            final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_DIR_CREATE_FAIL", args);
            throw new IOException(msg);
        }
        final File file = new File(filename);
        file.createNewFile();
        try (final FileWriter writer = new FileWriter(file)) {
            writer.write(data);
            writer.flush();
        } catch (IOException ex) {
            throw ex;
        }
    }

    public static void copyFile(final File source, final File target) throws IOException {
        try (final FileInputStream is = new FileInputStream(source);
             final FileOutputStream os = new FileOutputStream(target)) {
            final byte[] buffer = new byte[1024];
            for (int length = is.read(buffer); length > 0; length = is.read(buffer)) {
                os.write(buffer, 0, length);
            }
            os.flush();
        }
    }
}
